{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "     pcost       dcost       gap    pres   dres\n",
      " 0: -4.1833e+01 -1.0616e+02  1e+03  4e+01  3e+00\n",
      " 1: -1.1831e+02 -1.6644e+02  8e+02  2e+01  1e+00\n",
      " 2: -7.8173e+01 -4.7184e+01  4e+02  1e+01  6e-01\n",
      " 3: -1.3011e+01 -1.8415e+00  7e+01  1e+00  9e-02\n",
      " 4: -7.5358e-01 -6.2071e-01  6e+00  1e-01  6e-03\n",
      " 5: -8.1534e-02 -4.7394e-01  2e+00  2e-02  2e-03\n",
      " 6: -7.1940e-02 -3.7996e-01  9e-01  1e-02  6e-04\n",
      " 7: -1.0824e-01 -2.2899e-01  3e-01  3e-03  2e-04\n",
      " 8: -1.4573e-01 -2.5759e-01  2e-01  1e-03  8e-05\n",
      " 9: -1.7657e-01 -2.1577e-01  5e-02  2e-04  1e-05\n",
      "10: -1.8586e-01 -2.1261e-01  3e-02  1e-04  6e-06\n",
      "11: -2.0454e-01 -2.0736e-01  3e-03  8e-06  5e-07\n",
      "12: -2.0682e-01 -2.0688e-01  7e-05  1e-07  8e-09\n",
      "13: -2.0687e-01 -2.0687e-01  1e-06  2e-09  1e-10\n",
      "14: -2.0687e-01 -2.0687e-01  3e-08  2e-11  1e-12\n",
      "Optimal solution found.\n",
      "8 support vectors out of 180 points\n",
      "20 out of 20 predictions correct\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "#!/usr/bin/env python3\n",
    "# -*- encoding: utf-8 -*-\n",
    "\n",
    "'''\n",
    "@author: louwill\n",
    "@contact: ygnjd2016@gmail.com\n",
    "@file: soft_margin_svm.py\n",
    "@time: 2019/4/23 21:07\n",
    "'''\n",
    "\n",
    "import numpy as np\n",
    "from numpy import linalg\n",
    "import cvxopt\n",
    "import cvxopt.solvers\n",
    "import pylab as pl\n",
    "\n",
    "\n",
    "\n",
    "def linear_kernel(x1, x2):\n",
    "    return np.dot(x1, x2)\n",
    "\n",
    "\n",
    "def polynomial_kernel(x, y, p=3):\n",
    "    return (1 + np.dot(x, y)) ** p\n",
    "\n",
    "\n",
    "def gaussian_kernel(x, y, sigma=5.0):\n",
    "    return np.exp(-linalg.norm(x - y) ** 2 / (2 * (sigma ** 2)))\n",
    "\n",
    "\n",
    "class soft_margin_svm(object):\n",
    "\n",
    "    def __init__(self, kernel=linear_kernel, C=None):\n",
    "        self.kernel = kernel\n",
    "        self.C = C\n",
    "        if self.C is not None:\n",
    "            self.C = float(self.C)\n",
    "\n",
    "    def fit(self, X, y):\n",
    "        n_samples, n_features = X.shape\n",
    "\n",
    "        # Gram matrix\n",
    "        K = np.zeros((n_samples, n_samples))\n",
    "        for i in range(n_samples):\n",
    "            for j in range(n_samples):\n",
    "                K[i, j] = self.kernel(X[i], X[j])\n",
    "\n",
    "        P = cvxopt.matrix(np.outer(y, y) * K)\n",
    "        q = cvxopt.matrix(np.ones(n_samples) * -1)\n",
    "        A = cvxopt.matrix(y, (1, n_samples))\n",
    "        b = cvxopt.matrix(0.0)\n",
    "\n",
    "        if self.C is None:\n",
    "            G = cvxopt.matrix(np.diag(np.ones(n_samples) * -1))\n",
    "            h = cvxopt.matrix(np.zeros(n_samples))\n",
    "        else:\n",
    "            tmp1 = np.diag(np.ones(n_samples) * -1)\n",
    "            tmp2 = np.identity(n_samples)\n",
    "            G = cvxopt.matrix(np.vstack((tmp1, tmp2)))\n",
    "            tmp1 = np.zeros(n_samples)\n",
    "            tmp2 = np.ones(n_samples) * self.C\n",
    "            h = cvxopt.matrix(np.hstack((tmp1, tmp2)))\n",
    "\n",
    "        # solve QP problem\n",
    "        solution = cvxopt.solvers.qp(P, q, G, h, A, b)\n",
    "\n",
    "        # Lagrange multipliers\n",
    "        a = np.ravel(solution['x'])\n",
    "\n",
    "        # Support vectors have non zero lagrange multipliers\n",
    "        sv = a > 1e-5\n",
    "        ind = np.arange(len(a))[sv]\n",
    "        self.a = a[sv]\n",
    "        self.sv = X[sv]\n",
    "        self.sv_y = y[sv]\n",
    "        print(\"%d support vectors out of %d points\" % (len(self.a), n_samples))\n",
    "\n",
    "        # Intercept\n",
    "        self.b = 0\n",
    "        for n in range(len(self.a)):\n",
    "            self.b += self.sv_y[n]\n",
    "            self.b -= np.sum(self.a * self.sv_y * K[ind[n], sv])\n",
    "        self.b /= len(self.a)\n",
    "\n",
    "        # Weight vector\n",
    "        if self.kernel == linear_kernel:\n",
    "            self.w = np.zeros(n_features)\n",
    "            for n in range(len(self.a)):\n",
    "                self.w += self.a[n] * self.sv_y[n] * self.sv[n]\n",
    "        else:\n",
    "            self.w = None\n",
    "\n",
    "    def project(self, X):\n",
    "        if self.w is not None:\n",
    "            return np.dot(X, self.w) + self.b\n",
    "        else:\n",
    "            y_predict = np.zeros(len(X))\n",
    "            for i in range(len(X)):\n",
    "                s = 0\n",
    "                for a, sv_y, sv in zip(self.a, self.sv_y, self.sv):\n",
    "                    s += a * sv_y * self.kernel(X[i], sv)\n",
    "                y_predict[i] = s\n",
    "            return y_predict + self.b\n",
    "\n",
    "    def predict(self, X):\n",
    "        return np.sign(self.project(X))\n",
    "\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "\n",
    "    def gen_lin_separable_data():\n",
    "        # generate training data in the 2-d case\n",
    "        mean1 = np.array([0, 2])\n",
    "        mean2 = np.array([2, 0])\n",
    "        cov = np.array([[0.8, 0.6], [0.6, 0.8]])\n",
    "        X1 = np.random.multivariate_normal(mean1, cov, 100)\n",
    "        y1 = np.ones(len(X1))\n",
    "        X2 = np.random.multivariate_normal(mean2, cov, 100)\n",
    "        y2 = np.ones(len(X2)) * -1\n",
    "        return X1, y1, X2, y2\n",
    "\n",
    "\n",
    "    def gen_non_lin_separable_data():\n",
    "        mean1 = [-1, 2]\n",
    "        mean2 = [1, -1]\n",
    "        mean3 = [4, -4]\n",
    "        mean4 = [-4, 4]\n",
    "        cov = [[1.0, 0.8], [0.8, 1.0]]\n",
    "        X1 = np.random.multivariate_normal(mean1, cov, 50)\n",
    "        X1 = np.vstack((X1, np.random.multivariate_normal(mean3, cov, 50)))\n",
    "        y1 = np.ones(len(X1))\n",
    "        X2 = np.random.multivariate_normal(mean2, cov, 50)\n",
    "        X2 = np.vstack((X2, np.random.multivariate_normal(mean4, cov, 50)))\n",
    "        y2 = np.ones(len(X2)) * -1\n",
    "        return X1, y1, X2, y2\n",
    "\n",
    "\n",
    "    def gen_lin_separable_overlap_data():\n",
    "        # generate training data in the 2-d case\n",
    "        mean1 = np.array([0, 2])\n",
    "        mean2 = np.array([2, 0])\n",
    "        cov = np.array([[1.5, 1.0], [1.0, 1.5]])\n",
    "        X1 = np.random.multivariate_normal(mean1, cov, 100)\n",
    "        y1 = np.ones(len(X1))\n",
    "        X2 = np.random.multivariate_normal(mean2, cov, 100)\n",
    "        y2 = np.ones(len(X2)) * -1\n",
    "        return X1, y1, X2, y2\n",
    "\n",
    "\n",
    "    def split_train(X1, y1, X2, y2):\n",
    "        X1_train = X1[:90]\n",
    "        y1_train = y1[:90]\n",
    "        X2_train = X2[:90]\n",
    "        y2_train = y2[:90]\n",
    "        X_train = np.vstack((X1_train, X2_train))\n",
    "        y_train = np.hstack((y1_train, y2_train))\n",
    "        return X_train, y_train\n",
    "\n",
    "\n",
    "    def split_test(X1, y1, X2, y2):\n",
    "        X1_test = X1[90:]\n",
    "        y1_test = y1[90:]\n",
    "        X2_test = X2[90:]\n",
    "        y2_test = y2[90:]\n",
    "        X_test = np.vstack((X1_test, X2_test))\n",
    "        y_test = np.hstack((y1_test, y2_test))\n",
    "        return X_test, y_test\n",
    "\n",
    "\n",
    "    def plot_margin(X1_train, X2_train, clf):\n",
    "        def f(x, w, b, c=0):\n",
    "            # given x, return y such that [x,y] in on the line\n",
    "            # w.x + b = c\n",
    "            return (-w[0] * x - b + c) / w[1]\n",
    "\n",
    "        pl.plot(X1_train[:, 0], X1_train[:, 1], \"ro\")\n",
    "        pl.plot(X2_train[:, 0], X2_train[:, 1], \"bo\")\n",
    "        pl.scatter(clf.sv[:, 0], clf.sv[:, 1], s=100, c=\"g\")\n",
    "\n",
    "        # w.x + b = 0\n",
    "        a0 = -4;\n",
    "        a1 = f(a0, clf.w, clf.b)\n",
    "        b0 = 4;\n",
    "        b1 = f(b0, clf.w, clf.b)\n",
    "        pl.plot([a0, b0], [a1, b1], \"k\")\n",
    "\n",
    "        # w.x + b = 1\n",
    "        a0 = -4;\n",
    "        a1 = f(a0, clf.w, clf.b, 1)\n",
    "        b0 = 4;\n",
    "        b1 = f(b0, clf.w, clf.b, 1)\n",
    "        pl.plot([a0, b0], [a1, b1], \"k--\")\n",
    "\n",
    "        # w.x + b = -1\n",
    "        a0 = -4;\n",
    "        a1 = f(a0, clf.w, clf.b, -1)\n",
    "        b0 = 4;\n",
    "        b1 = f(b0, clf.w, clf.b, -1)\n",
    "        pl.plot([a0, b0], [a1, b1], \"k--\")\n",
    "\n",
    "        pl.axis(\"tight\")\n",
    "        pl.show()\n",
    "\n",
    "\n",
    "    def plot_contour(X1_train, X2_train, clf):\n",
    "        pl.plot(X1_train[:, 0], X1_train[:, 1], \"ro\")\n",
    "        pl.plot(X2_train[:, 0], X2_train[:, 1], \"bo\")\n",
    "        pl.scatter(clf.sv[:, 0], clf.sv[:, 1], s=100, c=\"g\")\n",
    "\n",
    "        X1, X2 = np.meshgrid(np.linspace(-6, 6, 50), np.linspace(-6, 6, 50))\n",
    "        X = np.array([[x1, x2] for x1, x2 in zip(np.ravel(X1), np.ravel(X2))])\n",
    "        Z = clf.project(X).reshape(X1.shape)\n",
    "        pl.contour(X1, X2, Z, [0.0], colors='k', linewidths=1, origin='lower')\n",
    "        pl.contour(X1, X2, Z + 1, [0.0], colors='grey', linewidths=1, origin='lower')\n",
    "        pl.contour(X1, X2, Z - 1, [0.0], colors='grey', linewidths=1, origin='lower')\n",
    "\n",
    "        pl.axis(\"tight\")\n",
    "        pl.show();\n",
    "\n",
    "\n",
    "    def test_linear():\n",
    "        X1, y1, X2, y2 = gen_lin_separable_data()\n",
    "        X_train, y_train = split_train(X1, y1, X2, y2)\n",
    "        X_test, y_test = split_test(X1, y1, X2, y2)\n",
    "\n",
    "        clf = SVM()\n",
    "        clf.fit(X_train, y_train)\n",
    "\n",
    "        y_predict = clf.predict(X_test)\n",
    "        correct = np.sum(y_predict == y_test)\n",
    "        print(\"%d out of %d predictions correct\" % (correct, len(y_predict)))\n",
    "\n",
    "        plot_margin(X_train[y_train == 1], X_train[y_train == -1], clf)\n",
    "\n",
    "\n",
    "    def test_non_linear():\n",
    "        X1, y1, X2, y2 = gen_non_lin_separable_data()\n",
    "        X_train, y_train = split_train(X1, y1, X2, y2)\n",
    "        X_test, y_test = split_test(X1, y1, X2, y2)\n",
    "\n",
    "        clf = soft_margin_svm(polynomial_kernel)\n",
    "        clf.fit(X_train, y_train)\n",
    "\n",
    "        y_predict = clf.predict(X_test)\n",
    "        correct = np.sum(y_predict == y_test)\n",
    "        print(\"%d out of %d predictions correct\" % (correct, len(y_predict)))\n",
    "\n",
    "        plot_contour(X_train[y_train == 1], X_train[y_train == -1], clf)\n",
    "\n",
    "\n",
    "    def test_soft():\n",
    "        X1, y1, X2, y2 = gen_lin_separable_overlap_data()\n",
    "        X_train, y_train = split_train(X1, y1, X2, y2)\n",
    "        X_test, y_test = split_test(X1, y1, X2, y2)\n",
    "\n",
    "        clf = soft_margin_svm(C=1000.1)\n",
    "        clf.fit(X_train, y_train)\n",
    "\n",
    "        y_predict = clf.predict(X_test)\n",
    "        correct = np.sum(y_predict == y_test)\n",
    "        print(\"%d out of %d predictions correct\" % (correct, len(y_predict)))\n",
    "\n",
    "        plot_contour(X_train[y_train == 1], X_train[y_train == -1], clf)\n",
    "\n",
    "\n",
    "    # test_linear()\n",
    "    test_non_linear()\n",
    "#     test_soft()\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
